/*****************************************************************************
 *   Copyright 2005 Tim A Wang
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 ****************************************************************************/
package com.jb.web.application;

import com.jb.faces.FacesException;
import com.jb.faces.FactoryFinder;
import com.jb.faces.application.Application;
import com.jb.faces.application.NavigationHandler;
import com.jb.faces.application.StateManager;
import com.jb.faces.application.ViewHandler;
import com.jb.faces.component.UIComponent;
import com.jb.faces.context.FacesContext;
import com.jb.faces.convert.Converter;
import com.jb.faces.el.MethodBinding;
import com.jb.faces.el.PropertyResolver;
import com.jb.faces.el.ValueBinding;
import com.jb.faces.el.VariableResolver;
import com.jb.faces.event.ActionListener;
import com.jb.faces.lifecycle.Lifecycle;
import com.jb.faces.lifecycle.LifecycleFactory;
import com.jb.faces.validator.Validator;
import com.jb.framework.PersistentManager;
import com.jb.framework.Scope;
import com.jb.framework.pm.XMLPersistentManager;
import com.jb.security.DummySecurityManager;
import com.jb.security.SecurityException;
import com.jb.security.SecurityManager;
import com.jb.util.ClassUtil;
import com.jb.web.application.configuration.Config;
import com.jb.web.application.configuration.ManagedBeanDef;
import com.jb.web.bean.Page;
import com.jb.web.context.WebContext;
import com.jb.web.context.WebContextFactory;
import com.jb.web.lifecycle.LifeCycleImpl;
import com.jb.web.session.Session;
import com.jb.web.session.SessionManager;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

import java.io.Serializable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * This is a globle instance of a web application. This class is responsible  for the configuration
 * of this web application. It is shared by all the threads and must be implemented in a thread
 * safe way.
 */
public class ApplicationImpl extends Application implements Scope, Serializable{
    /** Returns persistent manager for system */
     public static final String TOOL_BAR = "toolMenu";

    static private Log log = LogFactory.getLog(ApplicationImpl.class.getName());
    
    private ActionListener actionListener;

    //faces implemenation
    private ArrayList locales = new ArrayList();
    private Config configuration = Config.getInstance();

    //store application wide 
    private HashMap <String, Object> attributes = new HashMap<String, Object>();
    private Locale defaultLocale;
    private Map componentTypes = new HashMap();
    private Map converters = new HashMap();
    private Map validators = new HashMap();
    
    private StateManager stateManager;
    private String defaultRenderKitId = "com.jb.web.renderKit.id";
    private String messageBundle;
    private ViewHandler viewHandler = new ViewHandlerImpl();
    private SecurityManager securityManager = null;
    private ServletConfig config;
    //hide default constructor
    private ApplicationImpl() {
    }

    public ApplicationImpl(ServletConfig config) {
        this.config=config;
        //load variables
        WebContext ctx = new WebContext(config);
       // beanBag = ctx.getPersistentManager().findById(beanBag);
        ctx.setApplication(this);
        
        Iterator iter=Config.getInstance().getBeans().iterator();
        while(iter.hasNext()) {
            ManagedBeanDef mbd= (ManagedBeanDef) iter.next();
            if("application".equals(mbd.getScope())) {
                Object obj = mbd.createInstance();
                if(obj!=null)
                    this.addAttribute(mbd.getName(),obj);
            }
        }        
    }

    /**
     * Returns a default web context.
     *
     * @return Web context!
     */
    public static WebContext getDefaultContext() {
        return new WebContext();
    }

    public Page getToolPage(WebContext ctx) {
        return (Page) RootContainer.findBeanz(ctx, TOOL_BAR, Page.class);
    }
    /**
     * Setter for action listener
     *
     * @param listener
     */
    public void setActionListener(ActionListener listener) {
        actionListener = listener;
    }

    /**
     * Returns action listener for this application.
     *
     * @return default action listner.
     */
    public ActionListener getActionListener() {
        return actionListener;
    }

    /**
     * Returns personal persisten manager
     *
     * @param ctx runtime context.
     *
     * @return return persistent manager for this applications.
     */
    public PersistentManager getApplicationPM(WebContext ctx) {
        return new XMLPersistentManager(Config.getInstance().getSystemPersistentManager(), ctx.getApplicationTop());
    }

    /**
     * Sets the root directory for applications.
     *
     * @param ctx runtime context.
     *
     * @return returns application top level location.
     */
    public String getApplicationTop(WebContext ctx) {
        return configuration.getApplicationTop() + File.separatorChar +
        (((ctx.getSession() == null) || (ctx.getSession().getApplication() == null))
        ? configuration.getDefaultApplication() : ctx.getSession().getApplication().getId());
    }

    /**
     * Gets application attribute value for this application.
     *
     * @param key of the attribute!
     *
     * @return Attribute value stored in this application.
     */
    public Object getAttribute(String key) {
        return attributes.get(key);
    }

    /**
     * Return supported component types.
     *
     * @return iterator of supported component types.
     */
    public java.util.Iterator getComponentTypes() {
        return componentTypes.keySet().iterator();
    }

    /**
     * Returns configuration for this application.
     *
     * @return Configuration.
     */
    public Config getConfiguration() {
        return configuration;
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public java.util.Iterator getConverterIds() {
        return converters.keySet().iterator(); //subset
    }

    /**
     * Returns a list of supported convertor types.
     * @return convertor types.
     */
    public java.util.Iterator getConverterTypes() {
        return converters.keySet().iterator(); //subset
    }
    /**
     * Sets default locale.
     * @param defaultLocale new default application locale.
     */
    public void setDefaultLocale(Locale defaultLocale) {
        this.defaultLocale = defaultLocale;
    }
    /**
     * Returns application default locale.
     * @return default locale.
     */
    public Locale getDefaultLocale() {
        return defaultLocale;
    }
    
    /**
     * Sets id for the default render kit.
     *
     * @param defaultRenderKitId
     */
    public void setDefaultRenderKitId(String defaultRenderKitId) {
        this.defaultRenderKitId = defaultRenderKitId;
    }

    /**
     * Returns the id of the default renderkit.
     *
     * @return id fo default renderkit
     */
    public String getDefaultRenderKitId() {
        return defaultRenderKitId;
    }

    /**
     * Setters for Faces Message Bundle
     *
     * @param messageBundle name of the message bundle
     */
    public void setMessageBundle(String messageBundle) {
        this.messageBundle = messageBundle;
    }

    /**
     * Returns name of the message bundle.
     *
     * @return MessageBundle.
     */
    public String getMessageBundle() {
        return messageBundle;
    }

    /**
     * Returns security manager configured for this application.
     * @SecurityManager.
     *
     * @return Security manager.
     */
    public synchronized SecurityManager getSecurityManager()
        throws SecurityException {
        if (securityManager == null || !configuration.getSecurityManager().equals(securityManager.getClass().getName())) {
            try {
                securityManager = (SecurityManager) ClassUtil.createInstance(configuration.getSecurityManager());
            } catch (Exception e) {
                throw new SecurityException(e);
            }
        }
        if(securityManager==null) {
            securityManager =  new DummySecurityManager();
        }
        return securityManager;
    }

    /**
     * Sets state manager.
     *
     * @param stateManager
     */
    public void setStateManager(StateManager stateManager) {
        this.stateManager = stateManager;
    }

    /**
     * Returns state manager.
     *
     * @return state manager
     */
    public StateManager getStateManager() {
        return stateManager;
    }
    /**
     * Adds collection of supported locales
     * @param locales to support
     */
    public void setSupportedLocales(Collection <Locale> locales) {
        this.locales.addAll(locales);
    }
    /**
     * Returns iterator over supported locales.
     * @return iterator over supported locales.
     */
    public java.util.Iterator getSupportedLocales() {
        return locales.iterator();
    }

    /**
     * Returns persisten manager for templates
     *
     * @return
     */
    public PersistentManager getTemplatePM() {
        return new XMLPersistentManager(Config.getInstance().getSystemPersistentManager(), getTemplateTop());
    }

    /**
     * Returns a set of validators.
     *
     * @return list of validators.
     */
    public java.util.Iterator getValidatorIds() {
        return validators.keySet().iterator();
    }

    /**
     * Sets view handler for this application.
     *
     * @param viewHandler new view handler.
     */
    public void setViewHandler(ViewHandler viewHandler) {
        this.viewHandler = viewHandler;
    }

    /**
     * Retruns view handler bound to this application.
     *
     * @return ViewHandler
     */
    public ViewHandler getViewHandler() {
        return viewHandler;
    }

    /**
     * JSF implementation. Not used as far.
     *
     * @param componentType DOCUMENT ME!
     * @param componentClass DOCUMENT ME!
     */
    public void addComponent(java.lang.String componentType, java.lang.String componentClass) {
        componentTypes.put(componentType, componentClass);
    }

    /**
     * Adds a converterClass with a key
     *
     * @param converterId id of the converter.
     * @param converterClass class name for the converter.
     */
    public void addConverter(String converterId, String converterClass) {
        converters.put(converterId, converterClass);
    }

    public void addConverter(Class targetClass, String converterClass) {
        converters.put(targetClass, converterClass);
    }

    public void addValidator(String validatorId, String validatorClass) {
        validators.put(validatorId, validatorClass);
    }

    public Converter createConverter(String converterId) {
        if (converterId == null) {
            throw new IllegalArgumentException("Converter Id must be not null");
        }

        String className = (String) converters.get(converterId);

        if (className == null) {
            throw new IllegalStateException("Class not found for converter " + converterId);
        }

        try {
            return createConverter(ClassUtil.getClass(className));
        } catch (Exception e) {
            return null;
        }
    }
    /**
     * Factory methods for a convertor. Returns null if fails to instantiate
     * a convertor.
     * @param targetClass convert class.
     * @return a new convertor.
     */
    public Converter createConverter(Class targetClass) {
        try {
            return (Converter) targetClass.newInstance();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Create an validator for a given id. This is jsf stuff
     * 
     * @param validatorId id of validator to be created
     * @return Validatro created
     * 
     * @throws com.jb.faces.FacesException DOCUMENT ME!
     */
    public Validator createValidator(String validatorId)
        throws FacesException {
        if (validatorId == null) {
            throw new IllegalArgumentException("Valiator Id must be not null");
        }
        String className = (String) converters.get(validatorId);
        if (className == null) {
            throw new IllegalStateException("Class not found for valiator " + validatorId);
        }
        return (Validator) ClassUtil.createInstance(className);
       
    }

    //where we will save templates
    //return template top
    String getTemplateTop() {
        return configuration.getWebTop() + configuration.getTemplateTop();
    }

    //create web context per request
    synchronized WebContext makeContext(HttpServletRequest request, HttpServletResponse response, Lifecycle lifecycle) {
        WebContextFactory factory = (WebContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
        WebContext wCtx = (WebContext) factory.getFacesContext(request, response, lifecycle);
        wCtx.setApplication(this);

        return wCtx;
    }

    synchronized WebContext makeContext(HttpServletRequest request, HttpServletResponse response) {
        WebContextFactory factory = (WebContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
        WebContext wCtx = (WebContext) factory.getFacesContext(request, response, null);
        wCtx.setApplication(this);

        return wCtx;
    }
    /**
     * Opens a dialog box in a differnt window.
     * @param request http request
     * @param response
     * @throws com.jb.web.application.ApplicationException
     * @throws java.io.IOException
     */
    void openDialog(HttpServletRequest request, HttpServletResponse response)
        throws ApplicationException, IOException {
        new DialogBoxApps().process(this, request, response);
    }

    /**
    * Opens inspector. For internal use only.
    *
    * @param request
    * @param response
    *
    * @throws ApplicationException DOCUMENT ME!
    * @throws IOException DOCUMENT ME!
    */
    void openInspector(HttpServletRequest request, HttpServletResponse response)
        throws ApplicationException, IOException {
        LifeCycleImpl lifeCycle = new LifeCycleImpl();
        WebContext ctx = null;

        try {
            ctx = makeContext(request, response, lifeCycle);
            lifeCycle.execute(ctx);

            Page toolBar = ctx.getToolBar();

            if (toolBar != null) {
                toolBar.setVisible("false");
            }

            lifeCycle.render(ctx);
        } finally {
            if (ctx != null) {
                ctx.cleanUp();
            }
        }
    }
    
    void processService(HttpServletRequest request, HttpServletResponse response)
        throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("Staring process request");
        }

        Session session = SessionManager.findOrCreate(request);
        String applName = request.getParameter("applName");
        if (applName != null) {
           session.setApplicationName(applName);
        }
        WebContext ctx = makeContext(request, response);
      
        String serviceName = request.getParameter("serviceName");

        WebService webService = (WebService)ClassUtil.createInstance(serviceName);
            
        if(webService!=null)
            webService.process(ctx);
       
        ctx.cleanUp();

        if (log.isDebugEnabled()) {
            log.debug("Done process request");
        }
    }
        
    void process(HttpServletRequest request, HttpServletResponse response)
        throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("Staring process request");
        }

        Session session = SessionManager.findOrCreate(request);
        
        String applName = request.getParameter("applName");
        if (applName != null) {
           session.setApplicationName(applName);
        }

        boolean deployed = Config.getInstance().isDeployed() && (session == null);

        LifecycleFactory lifeCycleF = (LifecycleFactory)FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
        LifeCycleImpl lifeCycle = (LifeCycleImpl) lifeCycleF.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);

        WebContext ctx = makeContext(request, response, lifeCycle);

        if (deployed) {
            ctx.getSession().setRunTime(true);
        }

        ctx.setDeployed(deployed);

        if (new InternalProcessor().handleRequest(ctx, request, response)) {
            return;
        }
        lifeCycle.execute(ctx);

        Page page = (Page) ctx.getRespondTree();
        if (!deployed) {
            Page toolBar = ctx.getToolBar();

            if (toolBar != null) {
                toolBar.setVisible(ctx.isRunTime() ? "false" : "true");
            }
        }

        if ((page == null) && !ctx.isResponseComplete) {
            ctx.addErrorMessage(this, "Page not found : " + ctx.getApplicationName());
        } else if (!ctx.isResponseComplete) {
            lifeCycle.render(ctx);

            //TODO better logic should be placed here to keep history intellegent
            if (!ctx.isError() && (ctx.getRequestTree() != null) && (ctx.getRequestTree() != ctx.getRespondTree())) {
                ctx.getSession().setPageFrom(ctx.getRequestTree().getName());
            }
        }

        if (ctx.isError()) {
            PrintWriter out = response.getWriter();
            out.write(
                "<div class=\"jbPopupError\" style=\"display:block;\" onclick=\"this.style.display='none';\" id=\"jbError\" >" +
                ctx.getAllErrors() + "</div>");
        }

        ctx.cleanUp();

        if (log.isDebugEnabled()) {
            log.debug("Done process request");
        }
    }
    
    public void addAttribute(String key, Object value) {
        attributes.put(key, value);
    }
    
    public Map getVariables() {
        return this.attributes;
    }
    
    public void addVariable(String name, Object variable) {
        this.attributes.put(name, variable);
    }
    
    public void setNavigationHandler(NavigationHandler nh) {
        //
    }
    public NavigationHandler getNavigationHandler() {
        return null;
    }

    private PropertyResolver propertyResolver;
    private VariableResolver variableResolver;
    
    public void setPropertyResolver(PropertyResolver param) {
        this.propertyResolver = param;
    }

    public PropertyResolver getPropertyResolver() {
        return propertyResolver;
    }

    public void setVariableResolver(VariableResolver param) {
        this.variableResolver = param;
    }

    public VariableResolver getVariableResolver() {
        return variableResolver;
    }
    
    public ValueBinding createValueBinding(String bindingName) {
        return null;
    }
    
    public MethodBinding createMethodBinding(String bindingName, Class[] clazzes) {
        return null;
    }
    
    public UIComponent createComponent(ValueBinding vb, FacesContext ctx, String classType) {
        return null;
    }    
    
    public UIComponent createComponent(String classType) {
        return null;
    }
    
}
